package com.sqj.proxyPattern.custom;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class SqjProxy {
    public static final String ln="\r\n";

    public static Object newInstance(SqjClassLoader classLoader,Class<?> [] interfaces,SqjInvocationHandler h){
        try {
            String src=generateSrc(interfaces);
            String filePath = SqjProxy.class.getResource("").getPath();
            File file = new File(filePath + "$Proxy0.java");
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(src);
            fileWriter.flush();
            fileWriter.close();
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            //TODO
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> iterable = manage.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //把编译生成的.class文件加载到JVM中
            Class proxyClass=classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(SqjInvocationHandler.class);
            file.delete();

            //返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?> [] interfaces){
        StringBuffer sb = new StringBuffer();
        sb.append("package com.sqj.proxyPattern.custom;");
        sb.append("import com.sqj.proxyPattern.custom.Person;"+ln);
        sb.append("import java.lang.reflect.*;"+ln);
        sb.append("public class $Proxy0 implements "+interfaces[0].getName()+"{"+ln);
        sb.append("SqjInvocationHandler h;"+ln);
        sb.append("public $Proxy0(SqjInvocationHandler h) {"+ln);
            sb.append("this.h=h;"+ln);
        sb.append("}"+ln);
        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();
            StringBuffer paramNames = new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramClasses = new StringBuffer();
            for (int i = 0; i < params.length; i++) {
                Class<?> clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }
            sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
            sb.append("try {" + ln);
            sb.append("Method m=" + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") +
                    getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
            sb.append("}catch(Error _ex){ }");
            sb.append("catch(Throwable e){" + ln);
            sb.append("throw new UndeclaredThrowableException(e);" + ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(m.getReturnType()));
        }
        sb.append("}"+ln);
        sb.append("}");
        return sb.toString();
    }

    private static String getReturnEmptyCode(Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "return 0";
        }else if(returnClass==void.class){
            return "";
        }else {
            return "return null";
        }
    }

    private static Map<Class,Class> mappings=new HashMap<>();
    static {
        mappings.put(int.class,Integer.class);
    }

    private static String getCaseCode(String code,Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "(("+mappings.get(returnClass).getName()+")"+code+")."+returnClass.getSimpleName()+"Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz){
        return clazz!=void.class;
    }

    private static String toLowerFirstCase(String src){
        char[] chars = src.toCharArray();
        chars[0]+=32;
        return String.valueOf(chars);
    }
}
